Data Sources topic

Data Sources

Datasources are the only classes that touch the network, the filesystem, or device-only APIs. They inject directly into Riverpod providers — there is no repository layer.

Supabase

File Responsibility
supabase_datasource.dart Content + user progress (domains, topics, lessons, sessions, annotations, goals). The largest surface in the codebase.
org_datasource.dart Organizations, teams, members, invitations, certifications, learning paths.
entitlement_datasource.dart Feature-flag lookups keyed by plan + org + user.
grants_datasource.dart OAuth grants (Google, Notion, Slack, Lucca).
billing_datasource.dart Subscription state; RevenueCat synced back into Supabase.
user_prefs_datasource.dart Per-user preferences (last org, onboarding step, etc.).

AI providers

The client talks to three AI vendors directly (bring-your-own-key) or via the ai-proxy Supabase Edge Function (managed plan):

File Vendor
claude_datasource.dart Anthropic Claude Messages API
openai_datasource.dart OpenAI Chat Completions
gemini_datasource.dart Google Gemini
proxy_ai_datasource.dart Dutato's ai-proxy Edge Function (Claude on the managed plan)
ai_datasource.dart Sealed union — the provider factory picks an implementation based on selectedAiProvider state.

Local (native-only, stubbed on web)

File Backed by
local_embedding_datasource.dart ONNX Runtime (sentence-transformers MiniLM)
local_vector_store.dart SQLite + sqlite_vector
revenuecat_datasource.dart RevenueCat SDK

Each has a *_barrel.dart that switches between the real implementation and a stub at import time. Always import the barrel, never the underlying file directly — the barrel is what lets the web build ship without dart:io.

Pattern

A typical datasource looks like:

class SupabaseDatasource {
  SupabaseDatasource(this._client);
  final SupabaseClient _client;

  Future<List<Topic>> fetchTopics(String bookId) async {
    final response = await _client
        .from('topics')
        .select()
        .eq('book_id', bookId)
        .order('position');
    return (response as List)
        .map((row) => Topic.fromJson(row))
        .toList();
  }
}
  • Constructor-injected client.
  • Raw Supabase/Dio calls; no wrapping the result in a Result<T, E>. Exceptions bubble up and are handled by the caller via userFriendlyMessage.
  • Models are freezed + json_serializable so fromJson is generated.

Riverpod providers thread the datasource in once (providers.dart builds the singleton supabaseDatasource provider) and every screen/provider that needs it reads that provider.

Classes

AiDatasource Data Sources
Abstract interface for AI provider datasources.
OrgDatasource Data Sources
SupabaseDatasource Data Sources